`timescale 1ns/1ps
`default_nettype none

module gclk_gen_mbi5353 (
    // system signal
    input  wire         I_sclk,  // 125M
    input  wire         I_rst_n,
    // config
    input  wire [7:0]   I_cfg_gclk_low,   // 时钟低电平时钟数
    input  wire [7:0]   I_cfg_gclk_cycle, // 时钟整周期时钟数
    input  wire [19:0]  I_cfg_gclk_extra, // 额外时钟数
    input  wire [7:0]   I_cfg_last_high,  // 消影时钟整周期时钟数
    // gclk
    input  wire         I_gclk_reset,
    input  wire         I_gclk_start,//单周期脉冲，与VSYNC0状态同步
    input  wire [15:0]  I_init_delay,
    input  wire [5:0]   I_scan_num,
    input  wire [6:0]   I_group_num,
    input  wire [10:0]  I_gclk_num,
    input  wire [15:0]  I_line_delay,
    output wire         O_gclk_out,
    output wire         O_row_start,
    output wire         O_row_end,
    output wire [5:0]   O_row_num,
    output wire         O_frame_end
);
//------------------------Parameter----------------------
// fsm
localparam [2:0]
    IDLE  = 0,
    PREP  = 1,
    WAIT0 = 2,
    DISP  = 3,
    EXTRA = 4, // 消影GCLK
    WAIT1 = 5;

//------------------------Local signal-------------------
// fsm
reg  [2:0]  state;
reg  [2:0]  next;
reg  [15:0] cnt0;
reg  [10:0] gclk_num;
reg  [5:0]  row_num;
reg  [6:0]  group_num;
reg         last_row;
reg         last_group;
reg         row_start;
reg         row_end;
reg         frame_end;

// gclk
reg  [7:0]  gclk_low;
reg  [7:0]  gclk_cycle;
reg  [19:0] gclk_extra;
reg  [7:0]  gclk_cnt;
reg         gclk_out;
reg         gclk_neg;
reg  [7:0]  last_cnt;

//------------------------Instantiation------------------

//------------------------Body---------------------------
//{{{+++++++++++++++++++++fsm++++++++++++++++++++++++++++
assign O_row_start = row_start;
assign O_row_end   = row_end;
assign O_row_num   = row_num;
assign O_frame_end = frame_end;

// state
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        state <= IDLE;
    else if (I_gclk_reset)
        state <= IDLE;
    else
        state <= next;
end

// next
always @(*) begin
    case (state)
        IDLE: begin
            if (I_gclk_start)
                next = PREP;
            else
                next = IDLE;
        end

        PREP: begin
            next = WAIT0;
        end

        WAIT0: begin
            if (cnt0 == 1'b1)
                next = DISP;
            else
                next = WAIT0;
        end

        DISP: begin
            if (gclk_neg && gclk_num == 1'b1)
                next = EXTRA;
            else
                next = DISP;
        end

        EXTRA: begin
            if (gclk_out == 1'b1 && last_cnt == I_cfg_last_high)
                next = WAIT1;
            else
                next = EXTRA;
        end

        WAIT1: begin
            if (cnt0 != 1'b1)
                next = WAIT1;
            else if (~last_row)
                next = DISP;
            else if (~last_group)
                next = DISP;
            else
                next = IDLE;
        end

        default: begin
            next = IDLE;
        end
    endcase
end

// cnt0
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        cnt0 <= 1'b1;
    else if (state == IDLE)
        cnt0 <= 1'b1;
    else if (state == PREP || frame_end)
        cnt0 <= I_init_delay;//vsync到gclk输出的延迟
    else if (state == DISP)
        cnt0 <= I_line_delay;//行扫描间隔时间/消隐时间
    else if (cnt0 != 1'b1)
        cnt0 <= cnt0 - 1'b1;
end

// gclk_num
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        gclk_num <= 1'b1;
    else if (state == WAIT0)
        gclk_num <= I_gclk_num;
    else if (state == WAIT1)
        gclk_num <= I_gclk_num;
    else if (gclk_neg && gclk_num > 1'b1)
        gclk_num <= gclk_num - 1'b1;
end

// row_num
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        row_num <= 1'b1;
    else if (state == IDLE)
        row_num <= 1'b1;
    else if (state == WAIT1 && cnt0 == 1'b1) begin
        if (last_row)
            row_num <= 1'b1;
        else
            row_num <= row_num + 1'b1;
    end
end

// group_num
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        group_num <= 1'b1;
    else if (state == IDLE)
        group_num <= 1'b1;
    else if (state == WAIT1 && cnt0 == 1'b1 && last_row)
        if (last_group)
            group_num <= 1'b1;
        else
            group_num <= group_num + 1'b1;
end

// last_row
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        last_row <= 1'b0;
    else if (state == IDLE)
        last_row <= 1'b0;
    else if (row_num == I_scan_num)
        last_row <= 1'b1;
    else
        last_row <= 1'b0;
end

// last_group
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        last_group <= 1'b0;
    else if (state == IDLE)
        last_group <= 1'b0;
    else if (group_num == I_group_num)
        last_group <= 1'b1;
    else
        last_group <= 1'b0;
end

// row_start
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        row_start <= 1'b0;
    else if (state == WAIT0 && next == DISP)
        row_start <= 1'b1;
    else if (state == WAIT1 && next == DISP)
        row_start <= 1'b1;
    else
        row_start <= 1'b0;
end

// row_end
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        row_end <= 1'b0;
    else if (state == DISP && next == EXTRA)
        row_end <= 1'b1;
    else
        row_end <= 1'b0;
end

// frame_end
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        frame_end <= 1'b0;
    else if (state == IDLE)
        frame_end <= 1'b0;
    else if (state == WAIT1 && last_row && last_group && cnt0 == 2'd2)
        frame_end <= 1'b1;
    else
        frame_end <= 1'b0;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

//{{{+++++++++++++++++++++gclk+++++++++++++++++++++++++++
assign O_gclk_out = gclk_out;

// gclk_low
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        gclk_low <= 1'b1;
    else if (state == PREP)
        gclk_low <= I_cfg_gclk_low;
end

// gclk_cycle
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        gclk_cycle <= 1'b1;
    else if (state == PREP)
        gclk_cycle <= I_cfg_gclk_cycle;
end

// gclk_extra
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        gclk_extra <= 1'b0;
    else if (state == PREP)
        gclk_extra <= I_cfg_gclk_extra;
    else if (gclk_neg && gclk_extra != 1'b0)
        gclk_extra <= gclk_extra - 1'b1;
end

// gclk_cnt
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        gclk_cnt <= 1'b1;
    else if (state == IDLE)
        gclk_cnt <= 1'b1;
    else if (state == DISP) begin
        if (!gclk_neg)
            gclk_cnt <= gclk_cnt + 1'b1;
        else if (gclk_extra != 1'b0)
            gclk_cnt <= 1'b0;
        else
            gclk_cnt <= 1'b1;
    end
end

// gclk_out
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        gclk_out <= 1'b0;
    else if (state == DISP) begin
        if (gclk_cnt == gclk_low)
            gclk_out <= 1'b1;
        else if (gclk_neg)
            gclk_out <= 1'b0;
    end
    else if (state == EXTRA) begin
        if (gclk_out == 1'b0 && last_cnt == gclk_low)
            gclk_out <= 1'b1;
        else if (gclk_out == 1'b1 && last_cnt == I_cfg_last_high)
            gclk_out <= 1'b0;
    end
    else
        gclk_out <= 1'b0;
end

// last_cnt
always @(posedge I_sclk or negedge I_rst_n) begin
    if (!I_rst_n)
        last_cnt <= 1'b1;
    else if (state == EXTRA) begin
        if (gclk_out == 1'b0 && last_cnt == gclk_low)
            last_cnt <= 1'b1;
        else
            last_cnt <= last_cnt + 1'b1;
    end
    else
        last_cnt <= 1'b1;
end

// gclk_neg
always @(posedge I_sclk or negedge I_rst_n) begin
    if (~I_rst_n)
        gclk_neg <= 1'b0;
    else if (gclk_cnt == gclk_cycle - 1'b1)
        gclk_neg <= 1'b1;
    else
        gclk_neg <= 1'b0;
end
//}}}++++++++++++++++++++++++++++++++++++++++++++++++++++

endmodule

`default_nettype wire

// vim:set ts=4 sw=4 et fenc=utf-8 fdm=marker:
